//
//  NMEvent.swift
//  NetEaseMusic
//
//  Created by SAGESSE on 2020/1/13.
//  Copyright © 2020 SAGESSE. All rights reserved.
//

import UIKit

class NMEvent: Decodable {
    
    typealias CustomKey = JSONDecoderEx.CustomKey

    
    var id: Int
    var type: Int

    var sender: NMUser
    var entitys: [Any]

////    var uuid: String?
//
//
//    var expireTime: Int = 0
//
//    //var rcmdInfo: String?
//    var json: String?
//
//    var lotteryEventData: String?
//    var showTime: Int = 0
//
//    var tmplId: Int = 0
//
//    var topEvent: Bool = false
//
////    var info: Any?
//
//    var actId: Int = 0
//    var type: Int = 0
//    var insiteForwardCount: Int = 0
//    var eventTime: Int = 1578384723816
//
//
//    var forwardCount: Int = 0
//
    
    var date: Date
    
    
    var likes: Int = 0
    var shares: Int = 0
    var comments: Int = 0
    var forwardings: Int = 0
    
    var isLiked: Bool = false
    var isStickied: Bool = false
    var isReferenced: Bool = false

    required init(from decoder: Decoder) throws {
        
        let container = try decoder.container(keyedBy: JSONDecoderEx.CustomKey.self)
        
        self.id = try container.decode(Int.self, forKey: "id")
        self.type = try container.decode(Int.self, forKey: "type")
        
        self.sender = try container.decode(NMUser.self, forKey: "user")
        self.entitys = []
        
        self.date = try container.decode(Date.self, forKey: "showTime")
        
        let info = try container.nestedContainer(keyedBy: JSONDecoderEx.CustomKey.self, forKey: "info")
        
        self.likes = try info.decodeIfPresent(Int.self, forKey: "likedCount") ?? 0
        self.shares = try info.decodeIfPresent(Int.self, forKey: "shareCount") ?? 0
        self.comments = try info.decodeIfPresent(Int.self, forKey: "commentCount") ?? 0
        self.forwardings = try container.decodeIfPresent(Int.self, forKey: "insiteForwardCount") ?? 0
        
        self.isLiked = try info.decodeIfPresent(Bool.self, forKey: "liked") ?? false
        self.isStickied = try container.decodeIfPresent(Bool.self, forKey: "topEvent") ?? false
        self.isReferenced = false

        // Prase all tiles with builder.
        var caches = ["": container]
        self.entitys = Builder.registers.compactMap {
            // If the user specifies type information, first check the type.
            guard $0.type?.contains(self.type) ?? true else {
                return nil
            }
                
            // Fi1nd the correct parent container.
            var cur = container
            var range = $0.key.startIndex ..< $0.key.endIndex
            
            // Find next separator in the key.
            while let next = $0.key.range(of: ".", range: range), !next.isEmpty {
                // Gets the container in the key.
                let key = String($0.key[$0.key.startIndex ..< next.upperBound])
                if let newContainer = caches[key] {
                    cur = newContainer // Hit cache.
                    range = next.upperBound ..< range.upperBound
                    continue
                }
                
                // Missed, additional processing required.
                let subkey = $0.key[range.lowerBound ..< $0.key.index(before: next.upperBound)]
                guard let newContainer = try? cur.nestedJSONContainerIfPresent(forKey: .init(stringValue: .init(subkey))) else {
                    return nil // Unable to read, ignore this data.
                }
                cur = newContainer
                caches[key] = newContainer
                range = next.upperBound ..< range.upperBound
            }
            
            do {
                // When the last key does not exist, ignore.
                let key = String($0.key[range])
                guard cur.contains(.init(stringValue: key)) else {
                    return nil
                }
                //print(#function, "\($0.key) \($0.type as Any? ?? "<not specified>") \(self.type)")

                // Parsing entity data for tile..
                return try $0.display(cur.superDecoder(forKey: .init(stringValue: key)))
                
            } catch (let exception as Exception) {
                // This is an exception that can be displayed.
                exception.resolved = $0.resolved
                return exception
                
            } catch (let error) {
                // An unknown error occurred.
                print(#function, error)
                return nil
            }
        }
        
        //var pendantData: Any? {
        //  "id": 27000,
        //  "imageUrl": "http://p1.music.126.net/69ADY7MgreUf9Owg_drahQ==/109951164219713265.jpg",
        //  "imageAndroidUrl": "http://p1.music.126.net/fjHfw3T8jf_7SofWsI9dDg==/109951164255813306.jpg",
        //  "imageIosUrl": "http://p1.music.126.net/p1fmWjaOMCfP8wIXPMnbgw==/109951164255813310.jpg"
        //}
        if let pendant = try container.decodeIfPresent(NMUser.Pendant.self, forKey: "pendantData") {
            self.sender.pendant = pendant.imageUrl
        }
        
        self.fix1()
    }
    
    func fix1() {
        
        guard let src = self.entitys.firstIndex(where: { $0 is Referenced }), let refer = self.entitys[src] as? Referenced else {
            return
        }
        guard let dest = refer.event.entitys.firstIndex(where: { $0 is Quoted }), let quoted = refer.event.entitys[dest] as? Quoted else {
            return
        }
        
        self.entitys.forEach {
            guard let text = $0 as? Text else {
                return
            }
            let text2 = refer.event.entitys.first(where: { $0 is Text }) as? Text
            text.value += "//\(text2?.value(by: refer.event) ?? "")"
        }
        
        self.entitys.remove(at: src)
        self.entitys.append(quoted)
    }
}


// MARK: -


extension NMEvent {
    
    /// The base tile content entity.
    class Entity<T>: NMEventNodeEntity where T: NMEventNodeDisplayable {
        typealias Display = T
    }
    
    /// The exception content entity.
    class Exception: Entity<NMEventNode.Display>, Error {
        
        var error: Error
        var resolved: ((Exception, NMEvent) throws -> Any)?

        init(_ error: Error) {
            self.error = error
        }
        init(_ error: String) {
            self.error = NSError(domain: "NMEvent.Exception", code: -1, userInfo: [
                NSLocalizedDescriptionKey: error
            ])
        }
    }
    
    
    ///// The text content entity.
    class Text: Entity<NMEventNode.Display.Text>, NMEventNodeDecodable {
        
        var value: String
        
        func value(by event: NMEvent) -> String {
            
            guard event.isReferenced else {
                return value
            }
            
            var name = "@\(event.sender.nickname)"
            
            if let type = NMEvent.namesOfType[event.type], !type.isEmpty {
                name = "\(name) \(type)"
            }
            
            return "\(name)：\(value)"
        }
        
        required init?(from decoder: Decoder) throws {
            // When message is empty, ignore this entity.
            if try decoder.singleValueContainer().decodeNil() {
                return nil
            }
            self.value = try String(from: decoder)
        }
    }
    
    /// The image content entity.
    class Image: Entity<NMEventNode.Display.Image>, NMEventNodeDecodable {
        
        class Item: Decodable {
            
            var url: URL?
            var size: CGSize
            var format: String
            
            var isGIF: Bool {
                return format.caseInsensitiveCompare("gif") == .orderedSame
            }
            var isLonger: Bool {
                return size.width > 170
                    && size.height > 170
                    && size.height / size.width > 3
            }
            
            required init(from decoder: Decoder) throws {
                
                let container = try decoder.container(keyedBy: CustomKey.self)
                
                self.url = try container.decodeIfPresent(URL.self, forKey: "originUrl")
                self.size = .init(width: try container.decode(CGFloat.self, forKey: "width"),
                                  height: try container.decode(CGFloat.self, forKey: "height"))
                self.format = try container.decode(String.self, forKey: "format")
            }
        }
        
        var items: [Item]
        required init?(from decoder: Decoder) throws {
            self.items = try Array<Item>(from: decoder)
            
            // When image item is empty, ignore this entity.
            if self.items.isEmpty {
                return nil
            }
        }
    }
    
    /// The video content entity.
    class Video<Source>: Entity<NMEventNode.Display.Video<Source>>, NMEventNodeDecodable where Source: Decodable {
        
        var source: Source
        
        var url: URL?
        var cover: URL?
        
        var size: CGSize
        var duration: TimeInterval
        
        var plays: Int
        
        required init(from decoder: Decoder) throws {
            self.source = try Source(from: decoder)
            switch self.source {
            case let video as NMMedia.Video:
                // When the video is deleted, an exception is thrown.
                if video.status == -1 {
                    throw Exception("该视频已删除")
                }
                
                self.url = video.url
                self.cover = video.cover
                self.size = video.size
                self.duration = video.duration
                self.plays = video.plays
                
            case let movie as NMMedia.Movie:
                // When the video is deleted, an exception is thrown.
                if movie.status == -1 {
                    throw Exception("该视频已删除")
                }
                
                self.url = movie.url
                self.cover = movie.cover
                self.size = movie.size
                self.duration = movie.duration
                self.plays = movie.plays

            default:
                throw NSError(domain: "1", code: -1, userInfo: nil)
            }
        }
    }
    
    /// The music content entity.
    class Music<Source>: Entity<NMEventNode.Display.Music<Source>>, NMEventNodeDecodable where Source: Decodable {
        
        var title: String
        var subtitle: String
        var cover: URL?
        
        var source: Source
        
        required init(from decoder: Decoder) throws {
            self.source = try Source(from: decoder)
            switch self.source {
            case let song as NMMedia.Song:
                
                var name = song.name
                if let tag = song.alias?.first, !tag.isEmpty {
                    name = "\(name) (\(tag))"
                }
                if let tag = song.transNames?.first, !tag.isEmpty {
                    name = "\(name) (\(tag))"
                }

                self.title = name
                self.subtitle = song.artists.reduce("") {
                    guard !$0.isEmpty else {
                        return $1.name
                    }
                    return "\($0)/\($1.name)"
                }
                self.cover = song.album?.picUrl
                
            case let album as NMMedia.Album:
                
                self.title = album.name
                self.subtitle = album.artist.name
                self.cover = album.picUrl
                
            case let playlist as NMMedia.Playlist:
                
                self.title = "{歌单}\(playlist.name)"
                self.subtitle = "by \(playlist.creator.nickname)"
                self.cover = playlist.coverImgUrl
                
            case let show as NMMedia.Show:
                
                self.title = "{演出}\(show.title)"
                self.subtitle = show.time.first?.description ?? ""
                self.cover = show.cover

            case let radio as NMMedia.Radio:
                
                self.title = "{\(radio.category)}\(radio.name)"
                self.subtitle = "by \(radio.dj?.nickname ?? "")"
                self.cover = radio.picUrl
                
            case let program as NMMedia.Program:
                
                self.title = program.name
                self.subtitle = "{\(program.radio.category)}\(program.radio.name)"
                self.cover = program.coverUrl
                

            default:
                throw NSError()
            }
        }
    }
    
    /// The referenced content entity.
    class Referenced: Entity<NMEventNode.Display.Referenced>, NMEventNodeDecodable {
        
        var event: NMEvent
        
        required init(from decoder: Decoder) throws {
            do {
                // Resolve the forwarded event.
                self.event = try NMEvent(from: decoder)
                self.event.isReferenced = true
                
            } catch DecodingError.valueNotFound(_, let context) where (context.codingPath.first as? CustomKey)?.stringValue == "event" {
                // When the "event" could not be found, the event was deleted.
                throw Exception("该动态己删除")
                
            } catch (let error) {
                // An unknown error occurred.
                throw error
            }
        }
    }
}


// MARK: -


extension NMEvent {
    
    /// The music log content entity.
    class Log: Entity<NMEventNode.Display.Log>, NMEventNodeDecodable {
        
        var log: NMMedia.Log
        required init(from decoder: Decoder) throws {
            self.log = try NMMedia.Log(from: decoder)
        }
    }

    /// The comment content entity.
    class Quoted: Entity<NMEventNode.Display.Quoted>, NMEventNodeDecodable {
        
        var name: String
        var cover: URL?
        var contents: NMMedia.Quoted
        
        var song: NMMedia.Song?
        
        required init(from decoder: Decoder) throws {
            do {
                // Resolve the quote event.
                let container = try decoder.container(keyedBy: CustomKey.self)
                            
                self.contents = try NMMedia.Quoted(from: decoder)
                self.song = try container.decoderIfPresent(forKey: "resourceInfo").flatMap {
                    return try NMMedia.Song(from: $0)
                }
                
                self.name = try container.decode(String.self, forKey: "resourceName")
                self.cover = self.song?.album?.picUrl

            } catch DecodingError.valueNotFound(_, let context) where (context.codingPath.first as? CustomKey)?.stringValue == "resource" {
                // When the "event" could not be found, the event was deleted.
                throw Exception("该评论己删除")
                
            } catch (let error) {
                // An unknown error occurred.
                throw error
            }
        }
    }
    
    
    /// The comment content entity.
    class Shared<Source>: Entity<NMEventNode.Display.Shared<Source>>, NMEventNodeDecodable where Source: Decodable {
        
        var title: String
        var subtitle: String
        var cover: URL?
        
        var source: Source
        
        required init(from decoder: Decoder) throws {
            self.source = try Source(from: decoder)
            switch self.source {
            case let webpage as NMMedia.Webpage:
                
                self.title = webpage.title
                self.subtitle = webpage.subTitle
                self.cover = webpage.coverImgUrl
                
            case let topic as NMMedia.Topic:
                
                self.title = topic.title
                self.subtitle = "by \(topic.creator.nickname)"
                self.cover = topic.coverUrl ?? topic.rectanglePicUrl

            default:
                throw NSError()
            }
        }
    }
}


// MARK: -


extension NMEvent {

    struct Builder {
        
        let key: String
        let type: Set<Int>?
        let display: (Decoder) throws -> Any?
        let resolved: (Exception, NMEvent) throws -> Any?

        init<T>(_ key: String, _ entityType: T.Type) where T: NMEventNodeDecodable, T: NMEventNodeEntity, T.Display: NMEventNodeDisplayable {
            self.key = key
            self.type = nil
            self.display = { try entityType.init(from: $0) }
            self.resolved = { try entityType.Display.exception($0, for: $1) }
        }
        init<T>(_ key: String, _ type: Set<Int>, _ entityType: T.Type) where T: NMEventNodeDecodable, T: NMEventNodeEntity, T.Display: NMEventNodeDisplayable {
            self.key = key
            self.type = type
            self.display = { try entityType.init(from: $0) }
            self.resolved = { try entityType.Display.exception($0, for: $1) }
        }
        
        static var registers: [Builder] = [
            
            .init("json.msg", NMEvent.Text.self),
            .init("pics", NMEvent.Image.self),
            
            .init("json.mv", NMEvent.Video<NMMedia.Movie>.self),
            .init("json.video", NMEvent.Video<NMMedia.Video>.self),
            
            .init("json.song", NMEvent.Music<NMMedia.Song>.self),
            .init("json.album", NMEvent.Music<NMMedia.Album>.self),
            .init("json.playlist", NMEvent.Music<NMMedia.Playlist>.self),
            
            .init("json.show", NMEvent.Music<NMMedia.Show>.self),
            .init("json.program", NMEvent.Music<NMMedia.Program>.self),
            
            .init("json.djRadio", NMEvent.Music<NMMedia.Radio>.self),
            .init("json.topic", NMEvent.Shared<NMMedia.Topic>.self),

            .init("json.resource", [57, 58], NMEvent.Log.self),
            .init("json.resource", [31, 32], NMEvent.Quoted.self),
            .init("json.resource", [56], NMEvent.Shared<NMMedia.Webpage>.self),

            .init("json.event", NMEvent.Referenced.self),
        ]
    }
    
//    func integritys() {
//
//        // Comment in forwarding must to be merge.
//        if let src = self.entitys?.first(where: { $0 is Referenced }), let refer = self.entitys?[src] as? Referenced {
//            if let dest = refer.event.entitys?.firstIndex(where: { $0 is Quoted }), let quoted = refer.ev  {
//
//                self.entitys?.append(refer.event.entitys[$0])
//
//            }
//        }
//    }
    
//
//    func integrity<T>(_ type: Int, entity: T.Type, exception: @autoclosure () -> Exception, configure: ((Exception.Display, Exception, NMEvent) -> ())? = nil) {
//
//        guard self.type == type, !(self.entitys?.contains(where: { $0 is T }) ?? false) else {
//            return
//        }
//
//        let newValue = exception()
//        newValue.confiugre = configure
//        self.entitys?.append(newValue)
//    }
    static var namesOfType: [Int: String] = [
        // NMSongEvent
        11: "喜欢音乐",
        18: "分享单曲",
        30: "分享单曲",
        // NMPlayListEvent
        12: "创建歌单",
        13: "分享歌单",
        14: "收藏歌单",
        15: "在听歌单",
        // NMDJEvent
        16: "创建节目",
        17: "分享节目",
        23: "收藏节目",
        // NMAlbumEvent
        19: "分享专辑",
        // NMArtistEvent
        20: "分享歌手",
        36: "分享歌手",
        // NMMVEvent
        21: "分享视频",
        // NMForwardEvent
        22: "转发",
        // NMSubjectEvent
        24: "分享专栏文章",
        // NMActivityEvent
        25: "分享话题",
        // NMDjRadioEvent
        27: "订阅电台",
        28: "分享电台",
        // NMCommentsEvent
        31: "分享评论",
        32: "分享评论",
        // NMShowEvent
        38:  "分享演出",
        //40 NMRcmdUserEvent
        // NMShortVideoEvent
        39: "发布视频",
        41: "分享视频",
        // NMLLiveEvent
        //42: "分享%@",
        //43: "发布%@",
        //50 NMRcmdUserEvent
        //51 NMRcmdUserEvent
        //52 NMRcmdUserKnownEvent
        //53 NMRcmdSingleExcellentUserEvent
        //54 NMImageAdEvent
        // NMVideoAdEvent
        55: "分享视频",
        // NMGeneralShareEvent
        56: "分享",
        // NMMLogEvent
        57: "发布Mlog",
        58: "分享Mlog",
        //59 NMNeayByFriendsListEvent
        //60 NMWelcomeCardEvent
        //62 NMFoldMoreEvent
        //63 NMRcmdUserVertEvent
    ]
}

